package pub.gll.plugin.asm.annotation;

import com.android.build.api.transform.DirectoryInput;
import com.android.build.api.transform.JarInput;
import com.android.build.api.transform.Status;
import com.android.build.api.transform.TransformInput;
import com.android.build.api.transform.TransformInvocation;
import com.google.common.collect.FluentIterable;
import com.google.common.io.Files;

import java.io.File;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import pub.gll.plugin.asm.model.AspectClassManager;
import pub.gll.plugin.asm.model.RuleConfigManager;
import pub.gll.plugin.asm.task.CollectJarTask;
import pub.gll.plugin.asm.task.CollectSrcTask;
import pub.gll.plugin.util.FileUtil;
import pub.gll.plugin.util.Log;

/**
 * 注解扫描处理器 {@link pub.gll.libaptannotation.Aspect}
 *
 * @author yuqiang
 */
public class AspectCollector {
    private static final String TAG = "AspectCollector";

    private final ExecutorService executor;

    private boolean isDirIncrement;
    private boolean isJarIncrement;

    public AspectCollector(ExecutorService executor) {
        this.executor = executor;
    }

    public void collect(TransformInvocation transformInvocation) throws ExecutionException, InterruptedException {
        List<Future> futures = new LinkedList<>();//所有的任务
        Collection<TransformInput> inputs = transformInvocation.getInputs();
        for (TransformInput input : inputs) {
            //dir
            dirCollect(input, transformInvocation);
            //jar
            jarCollect(input, transformInvocation);

            if (!isDirIncrement || !isJarIncrement) {//如果发现一个不是增量，则直接全量，停止遍历
                break;
            }
        }
        Log.i(TAG, "是否是增量  isDirIncrement=" + isDirIncrement + "  isJarIncrement=" + isJarIncrement);
        doCollect(inputs, futures);
        for (Future future : futures) {
            future.get();
        }
        futures.clear();
    }

    private void doCollect(Collection<TransformInput> inputs, List<Future> futures) {
        if (isDirIncrement && isJarIncrement) {//增量编译
            for (TransformInput input : inputs) {
                //dir
                doDirIncrementCollect(input, futures);
                //jar
                doJarIncremental(input, futures);
            }
            AspectClassManager.getInstance().setIsIncrement(true);
        } else {
            //全量时，先清除数据，重新收集
            clearData();
            for (TransformInput input : inputs) {
                //dir
                doDirFullCollect(input, futures);
                //jar
                doJarFullCollect(input, futures);
            }
            AspectClassManager.getInstance().setIsIncrement(false);
        }
    }

    private void clearData() {
        AspectClassManager.getInstance().clear();
        CollectArchBindingDataContainer.reset();
        RuleConfigManager.getInstance().clear();
    }

    //dir 资源收集
    private void dirCollect(TransformInput input, TransformInvocation transformInvocation) {
        boolean isNeedIncremental = false;
        if (transformInvocation.isIncremental()) {
            if (input.getDirectoryInputs().isEmpty()) {
                isDirIncrement = transformInvocation.isIncremental();
                return;
            }
            for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
                Map<File, Status> fileStatusMap = directoryInput.getChangedFiles();
                Set<String> files = fileStatusMap.keySet().stream().map(file -> file.getAbsolutePath()).collect(Collectors.toSet());
                //如果是增量编译并且改变的文件不包含aspect类，则执行增量编译否则全量编译
                if (!AspectClassManager.getInstance().contains(files)) {
                    isNeedIncremental = true;
                } else {//全量编译
                    isNeedIncremental = false;
                    break;
                }
            }
        }
        isDirIncrement = isNeedIncremental;
    }


    private void doDirIncrementCollect(TransformInput input, List<Future> futures) {
        for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
            Map<File, Status> fileStatusMap = directoryInput.getChangedFiles();
            for (Map.Entry<File, Status> changedFile : fileStatusMap.entrySet()) {
                Status status = changedFile.getValue();
                File inputFile = changedFile.getKey();
                if (status == Status.ADDED || status == Status.CHANGED) {
                    futures.add(executor.submit(new CollectSrcTask(inputFile, status)));
                }
            }
        }
    }


    private void doDirFullCollect(TransformInput input, List<Future> futures) {
        for (DirectoryInput directoryInput : input.getDirectoryInputs()) {
            File dirInput = directoryInput.getFile();
            if (dirInput.isDirectory()) {
                for (File classFile : getAllFiles(dirInput)) {
                    futures.add(executor.submit(new CollectSrcTask(classFile, Status.ADDED)));
                }
            } else {
                futures.add(executor.submit(new CollectSrcTask(dirInput, Status.ADDED)));
            }
        }
    }
    public static FluentIterable<File> getAllFiles(File dir) {
        return FluentIterable.from(Files.fileTraverser().depthFirstPreOrder(dir))
                .filter(Files.isFile());
    }



    //jar 资源收集
    private void jarCollect(TransformInput input, TransformInvocation transformInvocation) {
        final boolean[] isContainAspect = {false};//是否包含aspect
        boolean[] isIncrement = {false};
        if (transformInvocation.isIncremental()) {
            if (input.getJarInputs().isEmpty()) {
                isJarIncrement = transformInvocation.isIncremental();
                return;
            }
            for (JarInput inputJar : input.getJarInputs()) {
                Status status = inputJar.getStatus();
                if (status == Status.REMOVED || status == Status.ADDED || status == Status.CHANGED) {//如果为增量且是remove或者Added或者Changed,则判断里面是否有Aspect类
                    HashMap<ZipFile, Set<ZipEntry>> zipEntries = zipFile(inputJar.getFile());
                    zipEntries.forEach((zipFile, zipEntries1) -> {
                        Set<String> setEntryName = zipEntries1.stream().map(zipEntry -> zipEntry.getName()).collect(Collectors.toSet());
                        isContainAspect[0] = AspectClassManager.getInstance().contains(setEntryName);
                        isIncrement[0] = !isContainAspect[0];
                        if (!isIncrement[0]) {
                            return;
                        }
                    });
                } else {
                    isIncrement[0] = true;
                }
                if (!isIncrement[0]) {
                    break;
                }
            }
        }
        //不包含则认为是增量
        isJarIncrement = isIncrement[0];
    }

    private void doJarFullCollect(TransformInput input, List<Future> futures) {
        for (JarInput inputJar : input.getJarInputs()) {
            futures.add(executor.submit(new CollectJarTask(inputJar.getFile(), Status.ADDED)));
        }
    }

    private void doJarIncremental(TransformInput input, List<Future> futures) {
        for (JarInput inputJar : input.getJarInputs()) {
            Status status = inputJar.getStatus();
            if (status == Status.ADDED || status == Status.CHANGED) {
                futures.add(executor.submit(new CollectJarTask(inputJar.getFile(), status)));
            }
        }
    }


    private HashMap<ZipFile, Set<ZipEntry>> zipFile(File jarFile) {
        ZipFile zipFile = null;
        HashMap<ZipFile, Set<ZipEntry>> zipFileSetHashMap = new HashMap<>();
        Set<ZipEntry> zipEntries = new HashSet<>();
        try {
            zipFile = new ZipFile(jarFile);
            Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
            while (enumeration.hasMoreElements()) {
                ZipEntry zipEntry = enumeration.nextElement();
                zipEntries.add(zipEntry);
            }
            zipFileSetHashMap.put(zipFile, zipEntries);
        } catch (Exception e) {
            Log.e(TAG, " Jar error : " + e.getMessage());
            e.printStackTrace();
        } finally {
            FileUtil.closeQuietly(zipFile);
        }
        return zipFileSetHashMap;
    }
}
